<!doctype html>

<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

  <script src="../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <link rel="import" href="../../test-fixture/test-fixture.html">

  <link rel="import" href="helpers.html">
  <link rel="import" href="../vaadin-grid.html">
  <link rel="import" href="../vaadin-grid-selection-column.html">
  <link rel="import" href="../vaadin-grid-column-group.html">
  <link rel="import" href="../../polymer/lib/utils/render-status.html">

  <style>
    vaadin-grid {
      height: 400px;
      width: 800px;
    }
  </style>
</head>

<body>

  <dom-module id="grid-wrapper">
    <template>
      <vaadin-grid id="grid" size="10">
        <slot name="grid"></slot>
        <vaadin-grid-column-group>
          <template class="header">wrapper group header</template>
          <template class="footer">wrapper group footer</template>
          <slot name="group"></slot>
          <vaadin-grid-column>
            <template class="header">wrapper column header</template>
            <template class="footer">wrapper column footer</template>
            <template>wrapper column body [[item.value]]</template>
          </vaadin-grid-column>
        </vaadin-grid-column-group>
      </vaadin-grid>
    </template>
    <script>
      customElements.whenDefined('vaadin-grid').then(() => Polymer({is: 'grid-wrapper'}));
    </script>
  </dom-module>

  <dom-module id="x-boolean-toggle">
    <template>[[value]]</template>
    <script>
      customElements.whenDefined('vaadin-grid').then(() => {
        class XBooleanToggle extends Polymer.Element {
          static get is() {
            return 'x-boolean-toggle';
          }

          static get properties() {
            return {
              value: {type: Boolean, notify: true}
            };
          }
        }
        customElements.define(XBooleanToggle.is, XBooleanToggle);
      });
    </script>
  </dom-module>

  <test-fixture id="column">
    <template>
      <vaadin-grid-column>
        <template class="header">some header</template>
        <template class="footer">some footer</template>
        <template>some body [[item.value]]</template>
      </vaadin-grid-column>
    </template>
  </test-fixture>

  <test-fixture id="selection-column">
    <template>
      <vaadin-grid-selection-column>
        <template class="header">some header</template>
        <template>some body [[item.value]]</template>
        <template class="footer">some footer</template>
      </vaadin-grid-selection-column>
    </template>
  </test-fixture>

  <test-fixture id="group">
    <template>
      <vaadin-grid-column-group>
        <template class="header">some group header</template>
        <template class="footer">some group footer</template>
        <vaadin-grid-column>
          <template class="header">some foo header</template>
          <template class="footer">some foo footer</template>
          <template>some foo body [[item.value]]</template>
        </vaadin-grid-column>
        <vaadin-grid-column>
          <template class="header">some bar header</template>
          <template class="footer">some bar footer</template>
          <template>some bar body [[item.value]]</template>
        </vaadin-grid-column>
      </vaadin-grid-column-group>
    </template>
  </test-fixture>

  <test-fixture id="columns">
    <template>
      <vaadin-grid size="10">
        <vaadin-grid-column>
          <template class="header">first header</template>
          <template class="footer">first footer</template>
          <template>first body [[item.value]]</template>
        </vaadin-grid-column>
        <vaadin-grid-column>
          <template class="header">second header</template>
          <template class="footer">second footer</template>
          <template>second body [[item.value]]</template>
        </vaadin-grid-column>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="plain-groups">
    <template>
      <vaadin-grid size="10">
        <vaadin-grid-column-group>
          <template class="header">first group header</template>
          <template class="footer">first group footer</template>
          <vaadin-grid-column>
            <template class="header">first foo header</template>
            <template class="footer">first foo footer</template>
            <template>first foo body [[item.value]]</template>
          </vaadin-grid-column>
          <vaadin-grid-column>
            <template class="header">first bar header</template>
            <template class="footer">first bar footer</template>
            <template>first bar body [[item.value]]</template>
          </vaadin-grid-column>
        </vaadin-grid-column-group>
        <vaadin-grid-column-group>
          <template class="header">second group header</template>
          <template class="footer">second group footer</template>
          <vaadin-grid-column>
            <template class="header">second foo header</template>
            <template class="footer">second foo footer</template>
            <template>second foo body [[item.value]]</template>
          </vaadin-grid-column>
          <vaadin-grid-column>
            <template class="header">second bar header</template>
            <template class="footer">second bar footer</template>
            <template>second bar body [[item.value]]</template>
          </vaadin-grid-column>
        </vaadin-grid-column-group>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="nested-groups">
    <template>
      <vaadin-grid size="10">
        <vaadin-grid-column-group>
          <template class="header">group header</template>
          <template class="footer">group footer</template>
          <vaadin-grid-column-group>
            <template class="header">first nested group header</template>
            <template class="footer">first nested group footer</template>
            <vaadin-grid-column>
              <template class="header">first foo header</template>
              <template class="footer">first foo footer</template>
              <template>first foo body [[item.value]]</template>
            </vaadin-grid-column>
            <vaadin-grid-column>
              <template class="header">first bar header</template>
              <template class="footer">first bar footer</template>
              <template>first bar body [[item.value]]</template>
            </vaadin-grid-column>
          </vaadin-grid-column-group>
          <vaadin-grid-column-group>
            <template class="header">second nested group header</template>
            <template class="footer">second nested group footer</template>
            <vaadin-grid-column>
              <template class="header">second foo header</template>
              <template class="footer">second foo footer</template>
              <template>second foo body [[item.value]]</template>
            </vaadin-grid-column>
            <vaadin-grid-column>
              <template class="header">second bar header</template>
              <template class="footer">second bar footer</template>
              <template>second bar body [[item.value]]</template>
            </vaadin-grid-column>
          </vaadin-grid-column-group>
        </vaadin-grid-column-group>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="dom-repeat-columns">
    <template>
      <vaadin-grid id="grid" size="10">
        <dom-repeat as="column">
          <template is="dom-repeat" as="column">
            <vaadin-grid-column>
              <template class="header">grid repeats column [[column]] header</template>
              <template class="footer">grid repeats column [[column]] footer</template>
              <template>grid repeats column [[column]] body [[item.value]]</template>
            </vaadin-grid-column>
          </template>
        </dom-repeat>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="dom-repeat-columns-in-group">
    <template>
      <vaadin-grid size="10">
        <vaadin-grid-column-group>
          <dom-repeat as="column">
            <template is="dom-repeat" as="column">
              <vaadin-grid-column>
                <template class="header">group repeats column [[column]] header</template>
                <template class="footer">group repeats column [[column]] footer</template>
                <template>group repeats column [[column]] body [[item.value]]</template>
              </vaadin-grid-column>
            </template>
          </dom-repeat>
        </vaadin-grid-column-group>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="dom-repeat-groups">
    <template>
      <vaadin-grid size="10">
        <dom-repeat as="column">
          <template is="dom-repeat" as="column">
            <vaadin-grid-column-group>
              <vaadin-grid-column>
                <template class="header">grid repeats group [[column]] header</template>
                <template class="footer">grid repeats group [[column]] footer</template>
                <template>grid repeats group [[column]] body [[item.value]]</template>
              </vaadin-grid-column>
            </vaadin-grid-column-group>
          </template>
        </dom-repeat>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="dom-repeat-groups-in-group">
    <template>
      <vaadin-grid size="10">
        <vaadin-grid-column-group>
          <dom-repeat as="column">
            <template is="dom-repeat" as="column">
              <vaadin-grid-column-group>
                <vaadin-grid-column>
                  <template class="header">group repeats group [[column]] header</template>
                  <template class="footer">group repeats group [[column]] footer</template>
                  <template>group repeats group [[column]] body [[item.value]]</template>
                </vaadin-grid-column>
              </vaadin-grid-column-group>
            </template>
          </dom-repeat>
        </vaadin-grid-column-group>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="dom-repeat-columns-detailed">
    <template>
      <vaadin-grid id="grid" size="10">
        <template class="row-details"><div class="details">grid repeats column with detail detail [[item.value]]</div></template>
        <vaadin-grid-column frozen width="20px">
          <template><x-boolean-toggle value={{detailsOpened}}></x-boolean-toggle></template>
          <template class="header">detail toggle</template>
        </vaadin-grid-column>
        <dom-repeat as="column">
          <template is="dom-repeat" as="column">
            <vaadin-grid-column>
              <template class="header">grid repeats column with detail [[column]] header</template>
              <template>grid repeats column with detail [[column]] body [[item.value]]</template>
            </vaadin-grid-column>
          </template>
        </dom-repeat>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="effective-children-columns">
    <template>
      <grid-wrapper>
        <vaadin-grid-column>
          <template class="header">foo header</template>
          <template class="footer">foo footer</template>
          <template>foo body [[item.value]]</template>
        </vaadin-grid-column>
        <vaadin-grid-column>
          <template class="header">bar header</template>
          <template class="footer">bar footer</template>
          <template>bar body [[item.value]]</template>
        </vaadin-grid-column>
      </grid-wrapper>
    </template>
  </test-fixture>

  <test-fixture id="effective-children-groups">
    <template>
      <grid-wrapper>
        <vaadin-grid-column-group>
          <template class="header">first group header</template>
          <template class="footer">first group footer</template>
          <vaadin-grid-column>
            <template class="header">first foo header</template>
            <template class="footer">first foo footer</template>
            <template>first foo body [[item.value]]</template>
          </vaadin-grid-column>
          <vaadin-grid-column>
            <template class="header">first bar header</template>
            <template class="footer">first bar footer</template>
            <template>first bar body [[item.value]]</template>
          </vaadin-grid-column>
        </vaadin-grid-column-group>
        <vaadin-grid-column-group>
          <template class="header">second group header</template>
          <template class="footer">second group footer</template>
          <vaadin-grid-column>
            <template class="header">second foo header</template>
            <template class="footer">second foo footer</template>
            <template>second foo body [[item.value]]</template>
          </vaadin-grid-column>
          <vaadin-grid-column>
            <template class="header">second bar header</template>
            <template class="footer">second bar footer</template>
            <template>second bar body [[item.value]]</template>
          </vaadin-grid-column>
        </vaadin-grid-column-group>
      </grid-wrapper>
    </template>
  </test-fixture>

  <script>
    describe('light dom observing', () => {
      let wrapper, grid, scroller, header, body, footer, repeater;

      function init(fixtureName, options) {
        grid = fixture(fixtureName);
        if (grid.$.grid) {
          // unwrap the <grid-wrapper>
          wrapper = grid;
          grid = grid.$.grid || grid;
        }

        // <template is="dom-template"> does not work in Polymer 2, while
        // <test-fixture> does not support <dom-template>.  We have to set
        // the fixture data manually.
        repeater = grid.querySelector('dom-repeat');
        if (repeater) {
          repeater.items = options.columns;
        }

        // Assign the slot to the <grid-wrapper> children.
        if (wrapper) {
          Array.from(wrapper.children).forEach(
            wrapperChild => wrapperChild.setAttribute('slot', options && options.inGroup ? 'group' : 'grid'));
        }

        scroller = grid.$.scroller;
        header = grid.$.header;
        body = grid.$.items;
        footer = grid.$.footer;

        grid.dataProvider = infiniteDataProvider;
        flushGrid(grid);
      }

      function expectFirstColumnHeader(columnName, level) {
        level = level || 0;
        expect(getCellContent(getRows(header)[level].cells[0]).textContent)
          .to.contain(columnName + ' header');
      }

      function expectFirstColumnFooter(columnName, level) {
        level = level || 0;
        const lastLevel = getRows(header).length - 1;
        expect(getCellContent(getRows(footer)[lastLevel - level].cells[0]).textContent)
          .to.contain(columnName + ' footer');
      }

      function expectFirstColumnBody(columnName) {
        expect(getCellContent(getRows(body)[0].cells[0]).textContent)
          .to.contain(columnName + ' body foo0');
        expect(getCellContent(getRows(body)[1].cells[0]).textContent)
          .to.contain(columnName + ' body foo1');
        expect(getCellContent(getRows(body)[2].cells[0]).textContent)
          .to.contain(columnName + ' body foo2');
      }

      function expectFirstColumn(columnName, level) {
        expectFirstColumnHeader(columnName, level);
        expectFirstColumnFooter(columnName, level);
        expectFirstColumnBody(columnName);
      }

      function expectNumberOfColumns(number) {
        const lastLevel = getRows(header).length - 1;
        expect(getRows(header)[lastLevel].cells.length).to.be.equal(number);
        expect(getRows(body)[0].cells.length).to.be.equal(number);
        expect(getRows(footer)[0].cells.length).to.be.equal(number);
      }

      describe('generic operations', () => {
        describe('columns inside grid', () => {
          beforeEach(done => {

            init('columns');
            flushGrid(grid);
            flush(done);
          });

          it('should support adding late', () => {
            const column = fixture('column');
            grid.insertBefore(column, grid.firstChild);
            flushGrid(grid);
            expectFirstColumn('some');
          });

          it('should support adding selection column late', () => {
            const column = fixture('selection-column');
            grid.insertBefore(column, grid.firstChild);
            flushGrid(grid);
            expectFirstColumn('some');
          });

          it('should support removing late', () => {
            const column = grid.querySelector('vaadin-grid-column');
            grid.removeChild(column);
            flushGrid(grid);
            expectFirstColumn('second');
          });

          it('should invoke node observer twice when removing columns', done => {
            const column = grid.querySelector('vaadin-grid-column');
            const spy = sinon.spy(grid._observer, 'callback');
            grid.removeChild(column);
            flushGrid(grid);
            animationFrameFlush(() => {
              // Once for the column and in effect of that, once for the removed cell content elements
              expect(spy.callCount).to.gte(2);
              done();
            });
          });

          it('should invoke node observer twice when adding columns', done => {
            const column = fixture('column');
            const spy = sinon.spy(grid._observer, 'callback');
            grid.insertBefore(column, grid.firstChild);
            flushGrid(grid);
            flush(() => {
              // Once for the column and in effect of that, once for the added cell content elements
              expect(spy.callCount).to.gte(2);
              done();
            });
          });

          it('should not invoke on row reorder', done => {
            grid.size = 100;
            flushGrid(grid);
            flush(() => {
              const spy = sinon.spy(grid._observer, 'callback');
              scrollToEnd(grid, () => {
                expect(spy.called).to.be.false;
                done();
              });
            });
          });

          it('should reuse column cells', () => {
            const content = getHeaderCellContent(grid, 0, 0);
            grid.appendChild(grid._columnTree[0][0]);
            flushGrid(grid);
            expect(getHeaderCellContent(grid, 0, 1)).to.equal(content);
          });

          it('should not create new cells', () => {
            const row = getRows(grid.$.items)[0];
            const spy = sinon.spy(grid, '_createCell');
            const content = getHeaderCellContent(grid, 0, 0);
            grid.appendChild(grid._columnTree[0][0]);
            flushGrid(grid);
            expect(spy.called).to.be.false;
          });

        });

        describe('columns inside group', () => {
          let firstGroup;

          beforeEach(done => {

            init('plain-groups');
            firstGroup = grid.querySelector('vaadin-grid-column-group');
            flush(done);
          });

          it('should support adding late', done => {
            const column = fixture('column');
            firstGroup.insertBefore(column, firstGroup.firstChild);

            flush(() => {
              expectFirstColumn('some', 1);
              done();
            });
          });

          it('should support removing late', done => {
            const column = firstGroup.querySelector('vaadin-grid-column');
            firstGroup.removeChild(column);
            Polymer.flush();

            flush(() => {
              expectFirstColumn('first bar', 1);
              done();
            });
          });
        });

        describe('groups inside grid', () => {
          beforeEach(() => {

            init('plain-groups');
          });

          it('should support adding late', done => {
            const group = fixture('group');
            grid.insertBefore(group, grid.firstChild);
            flushGrid(grid);

            flush(() => {
              expectFirstColumnHeader('some group', 0);
              expectFirstColumnFooter('some group', 0);
              expectFirstColumn('some foo', 1);
              done();
            });
          });

          it('should support removing late', done => {
            const group = grid.querySelector('vaadin-grid-column-group');
            grid.removeChild(group);
            Polymer.flush();

            flush(() => {
              expectFirstColumnHeader('second group', 0);
              expectFirstColumnFooter('second group', 0);
              expectFirstColumn('second foo', 1);
              done();
            });
          });
        });

        describe('groups inside group', () => {
          let firstGroup;

          beforeEach(() => {

            init('nested-groups');
            firstGroup = grid.querySelector('vaadin-grid-column-group');
          });

          it('should support adding late', done => {
            const group = fixture('group');
            firstGroup.insertBefore(group, firstGroup.firstChild);
            flushGrid(grid);

            flush(() => {
              expectFirstColumnHeader('some group', 1);
              expectFirstColumnFooter('some group', 1);
              expectFirstColumn('some foo', 2);
              done();
            });
          });

          it('should support removing late', done => {
            const group = firstGroup.querySelector('vaadin-grid-column-group');
            firstGroup.removeChild(group);
            Polymer.flush();

            flush(() => {
              expectFirstColumnHeader('second nested group', 1);
              expectFirstColumnFooter('second nested group', 1);
              expectFirstColumn('second foo', 2);
              done();
            });
          });
        });
      });

      function shouldSupportDomRepeat(prefix, columnsLevel) {
        it('should provide initial state', done => {
          repeater.render();

          flush(() => {
            expectFirstColumn(prefix + ' a', columnsLevel);
            expectFirstColumn('', columnsLevel);
            expectNumberOfColumns(3);
            done();
          });
        });

        it('should add columns late', done => {
          repeater.unshift('items', 'd');
          repeater.render();

          flush(() => {
            expectFirstColumn(prefix + ' d', columnsLevel);
            expectFirstColumn('', columnsLevel);
            expectNumberOfColumns(4);
            done();
          });
        });

        it('should remove columns late', done => {
          repeater.shift('items');
          repeater.render();

          flush(() => {
            expectFirstColumn(prefix + ' b', columnsLevel);
            expectFirstColumn('', columnsLevel);
            expectNumberOfColumns(2);
            done();
          });
        });

        it('should remove cell content', done => {
          const contentCount = grid.querySelectorAll('vaadin-grid-cell-content').length;
          repeater.shift('items');
          repeater.render();

          flushGrid(grid);
          flush(() => {
            Polymer.RenderStatus.afterNextRender(grid, () => {
              expect(grid.querySelectorAll('vaadin-grid-cell-content').length).to.be.below(contentCount);
              done();
            });
          });
        });
      }

      // Skipped due to https://github.com/vaadin/vaadin-grid/issues/1713
      const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
      !ios && describe('dom-repeat', () => {
        let columns;

        beforeEach(() => columns = 'a b c'.split(' '));

        describe('columns inside grid', () => {
          beforeEach(() => {

            init('dom-repeat-columns', {columns: columns});
          });

          shouldSupportDomRepeat('grid repeats column');
        });

        describe('columns inside group', () => {
          beforeEach(() => {

            init('dom-repeat-columns-in-group', {columns: columns});
          });

          shouldSupportDomRepeat('group repeats column', 1);
        });

        describe('groups inside grid', () => {
          beforeEach(() => {

            init('dom-repeat-groups', {columns: columns});
          });

          shouldSupportDomRepeat('grid repeats group', 1);
        });

        describe('groups inside group', () => {
          beforeEach(() => {

            init('dom-repeat-groups-in-group', {columns: columns});
          });

          shouldSupportDomRepeat('group repeats group', 2);
        });

        describe('with row detail', function() {
          beforeEach(function() {
            init('dom-repeat-columns-detailed', {columns: columns});
          });

          it('should obey the "detailsOpened" template property', function(done) {
            repeater.render();
            flush(function() {
              var row = getRows(grid.$.items)[0];
              var cell = getRowCells(row)[0];
              var toggle = getCellContent(cell).children[0];
              // open row details
              toggle.value = true;

              expect(grid.detailsOpenedItems).to.contain(row._item);
              done();
            });

          });
        });
      });

      describe('effective children', () => {
        function shouldSupportEffectiveChildren(inGroup) {
          describe('children mutations', () => {
            describe('with columns', () => {
              beforeEach(() => {

                init('effective-children-columns', {inGroup: inGroup});
              });

              it('should provide initial state', done => {
                flush(() => {
                  expectFirstColumn('foo', 1);
                  expectNumberOfColumns(3);
                  done();
                });
              });

              it('should support adding late', done => {

                const column = fixture('column');
                column.setAttribute('slot', inGroup ? 'group' : 'grid');
                wrapper.insertBefore(column, wrapper.firstChild);
                Polymer.flush();

                flush(() => {
                  expectFirstColumn('some', 1);
                  expectNumberOfColumns(4);
                  done();
                });
              });

              it('should support removing late', done => {
                const column = wrapper.querySelector('vaadin-grid-column');
                wrapper.removeChild(column);
                Polymer.flush();

                flush(() => {
                  expectFirstColumn('bar', 1);
                  expectNumberOfColumns(2);
                  done();
                });
              });
            });

            describe('with groups', () => {
              let firstGroup;

              beforeEach(() => {

                init('effective-children-groups', {inGroup: inGroup});
                firstGroup = wrapper.querySelector('vaadin-grid-column-group');
              });

              it('should provide initial state', done => {
                flush(() => {
                  expectFirstColumn('first foo', inGroup ? 2 : 1);
                  expectNumberOfColumns(5);
                  done();
                });
              });

              it('should support adding late', done => {
                const group = fixture('group');
                group.setAttribute('slot', inGroup ? 'group' : 'grid');
                wrapper.insertBefore(group, wrapper.firstChild);
                Polymer.flush();

                flush(() => {
                  expectFirstColumn('some foo', inGroup ? 2 : 1);
                  expectNumberOfColumns(7);
                  done();
                });
              });

              it('should support removing late', done => {
                wrapper.removeChild(firstGroup);
                Polymer.flush();

                flush(() => {
                  expectFirstColumn('second foo', inGroup ? 2 : 1);
                  expectNumberOfColumns(3);
                  done();
                });
              });
            });
          });

          describe('nested group mutations', () => {
            let firstGroup;

            beforeEach(() => {

              init('effective-children-groups', {inGroup: inGroup});
              firstGroup = wrapper.querySelector('vaadin-grid-column-group');
            });

            describe('with columns', () => {
              it('should support adding late', done => {
                const column = fixture('column');
                firstGroup.insertBefore(column, firstGroup.firstChild);


                flush(() => {
                  expectFirstColumn('some', inGroup ? 2 : 1);
                  expectNumberOfColumns(6);
                  done();
                });
              });

              it('should support removing late', done => {
                const column = firstGroup.querySelector('vaadin-grid-column');
                firstGroup.removeChild(column);
                Polymer.flush();

                flush(() => {
                  expectFirstColumn('first bar', inGroup ? 2 : 1);
                  expectNumberOfColumns(4);
                  done();
                });
              });
            });

            describe('with groups', () => {
              it('should support adding late', done => {
                const group = fixture('group');
                firstGroup.insertBefore(group, firstGroup.firstChild);
                Polymer.flush();

                flush(() => {
                  expectFirstColumn('some foo', inGroup ? 3 : 2);
                  expectNumberOfColumns(7);
                  done();
                });
              });

              it('should support removing late', done => {
                const group = fixture('group');
                firstGroup.insertBefore(group, firstGroup.firstChild);
                Polymer.flush();

                flush(() => {
                  expectNumberOfColumns(7);
                  firstGroup.removeChild(group);
                  Polymer.flush();

                  flush(() => {
                    expectFirstColumn('first foo', inGroup ? 2 : 1);
                    expectNumberOfColumns(5);
                    done();
                  });
                });
              });
            });
          });
        }

        describe('of grid', () => shouldSupportEffectiveChildren(false));

        describe('of group', () => shouldSupportEffectiveChildren(true));
      });
    });
  </script>

</body>

</html>
